﻿using System;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Research.Kinect.Nui;
using System.Collections.Generic;
using KinectGestureRecognizer.Helper;
using KinectGestureRecognizer.Gestures;


namespace KinectGestureRecognizer
{
    public class kinectController : GameComponent
    {

        #region Constants

        const int numberOfJointsToCollect = 10;

        #endregion

        #region Private Fields

        //SKeletons & Joints
        SkeletonFrame allSkeletons;
        SkeletonData trackedSkeleton;
        Joint leftHand, rightHand, head;

        //Joints Collectors
        JointCollector rightHandJoints;
        JointCollector leftHandJoints;

        //Class related fields
        Runtime nui;
        String message;
        Game game;
        TimeSpan lastGestureOccurred;

        #endregion

        #region Delegate & Event

        public delegate void gestureType(Object sender, IGesture gesture);

        public event gestureType gestureDetected;

        #endregion

        #region Public Properties;

        public bool skeletonReady { get; set; }

        public Vector2 leftHandScreenPosition { get; set; }
        public Vector2 rightHandScreenPosition { get; set; }
        public Vector2 headScreenPosition { get; set; }

        public Vector3 leftHandWorldPosition { get; set; }
        public Vector3 rightHandWorldPosition { get; set; }
        public Vector3 headWorldPosition { get; set; }

        public JointCollector getRightHandJointCollection { get { return rightHandJoints; } }
        public JointCollector getLeftHandJointCollection { get { return leftHandJoints; } }

        public int numberOfKinectEvents { get; set; }

        #endregion

        #region Constructor

        public kinectController(Game game) : base (game)
        {
            this.game = game;
            rightHandJoints = new JointCollector(numberOfJointsToCollect);
            leftHandJoints = new JointCollector(numberOfJointsToCollect);
            lastGestureOccurred = new TimeSpan();
        }

        #endregion

        #region Initialize & Uninitialize

        public void Initialize()
        {
            Runtime.Kinects.StatusChanged += new EventHandler<StatusChangedEventArgs>(Kinects_StatusChanged);
            SetupKinect();
        }

        public void Uninitialize()
        {
            if (Runtime.Kinects.Count != 0)
                nui.Uninitialize();
        }

        #endregion

        #region Initialize Kinect

        private void SetupKinect()
        {
            if (Runtime.Kinects.Count != 0)
            {
                nui = Runtime.Kinects[0];
                nui.Initialize(RuntimeOptions.UseSkeletalTracking);
                nui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);

                TransformSmoothParameters parameters = new TransformSmoothParameters();
                parameters.Smoothing = 0.5f;
                parameters.Correction = 0.3f;
                parameters.Prediction = 0.3f;
                parameters.JitterRadius = 0.5f;
                parameters.MaxDeviationRadius = 0.5f;
                nui.SkeletonEngine.SmoothParameters = parameters;
                nui.SkeletonEngine.TransformSmooth = true;
            }
        }

        void Kinects_StatusChanged(object sender, StatusChangedEventArgs e)
        {
            string message = "Status Changed: ";
            switch (e.Status)
            {
                case KinectStatus.Connected:
                    message += "Connected";
                    SetupKinect();
                    break;
                case KinectStatus.Disconnected:
                    message += "Disconnected";
                    nui.Uninitialize();
                    break;
                case KinectStatus.Error:
                    message += "Error";
                    break;
                case KinectStatus.NotPowered:
                    message += "Not Powered";
                    break;
                case KinectStatus.NotReady:
                    message += "Not Ready";
                    break;
                default:
                    if (e.Status.HasFlag(KinectStatus.Error))
                    {
                        message += "Kinect error";
                    }
                    break;
            }
            this.message = message;
        }

        #endregion

        #region GameComponent Update

        public override void Update(GameTime gameTime)
        {
            Vector3 firstRight = helperFunctions.vectorToVector3(rightHandJoints.getFirstJoint.Position);
            Vector3 lastRight = helperFunctions.vectorToVector3(rightHandJoints.getLastJoint.Position);
            float xDistanceRight = helperFunctions.Vector3XDistance(firstRight, lastRight);
            float yDistanceRight = helperFunctions.Vector3YDistance(firstRight, lastRight);
            
            if (xDistanceRight > 0.3f && yDistanceRight < 0.1f && firstRight.X < lastRight.X && gameTime.TotalGameTime - lastGestureOccurred > TimeSpan.FromSeconds(1))
            {
                onGestureDetected(new rightHandSwipeRight());
                lastGestureOccurred = gameTime.TotalGameTime;
            }
            else if (xDistanceRight > 0.3f && yDistanceRight < 0.1f && firstRight.X > lastRight.X && gameTime.TotalGameTime - lastGestureOccurred > TimeSpan.FromSeconds(1))
            {
                onGestureDetected(new rightHandSwipeLeft());
                lastGestureOccurred = gameTime.TotalGameTime;
            }
            else if (xDistanceRight < 0.1f && yDistanceRight > 0.3f && firstRight.Y < lastRight.Y && gameTime.TotalGameTime - lastGestureOccurred > TimeSpan.FromSeconds(1))
            {
                onGestureDetected(new rightHandSwipeUp());
                lastGestureOccurred = gameTime.TotalGameTime;
            }
            else if (xDistanceRight < 0.1f && yDistanceRight > 0.3f && firstRight.Y > lastRight.Y && gameTime.TotalGameTime - lastGestureOccurred > TimeSpan.FromSeconds(1))
            {
                onGestureDetected(new rightHandSwipeDown());
                lastGestureOccurred = gameTime.TotalGameTime;
            }
            
            base.Update(gameTime);
        }

        #endregion

        #region Kinect Events

        void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            numberOfKinectEvents++;
            allSkeletons = e.SkeletonFrame;

            if (allSkeletons != null)
            {
                trackedSkeleton = (from s in allSkeletons.Skeletons
                                                where s.TrackingState == SkeletonTrackingState.Tracked
                                                select s).FirstOrDefault();

                if (trackedSkeleton != null && trackedSkeleton.TrackingState == SkeletonTrackingState.Tracked)
                {
                    this.message = "Found Skeleton";
                    skeletonReady = true;
                    leftHand = trackedSkeleton.Joints[JointID.HandLeft];
                    rightHand = trackedSkeleton.Joints[JointID.HandRight];
                    head = trackedSkeleton.Joints[JointID.Head];

                    leftHandScreenPosition = helperFunctions.screenizeKinectPosition(leftHand.Position, game, 300);
                    rightHandScreenPosition = helperFunctions.screenizeKinectPosition(rightHand.Position, game, 300);
                    headScreenPosition = helperFunctions.screenizeKinectPosition(head.Position, game, 300);

                    leftHandWorldPosition = helperFunctions.vectorToVector3(leftHand.Position);
                    rightHandWorldPosition = helperFunctions.vectorToVector3(rightHand.Position);
                    headWorldPosition = helperFunctions.vectorToVector3(head.Position);

                    rightHandJoints.addJointToCollector(rightHand);
                    leftHandJoints.addJointToCollector(leftHand);
                }

                else
                    skeletonReady = false;
            }
        }

        #endregion

        #region Events Methods

        void onGestureDetected(IGesture gesture)
        {
            if (gestureDetected != null)
                gestureDetected(this, gesture);
        }

        #endregion

    }
}
